package SimpleXml;

import java.util.LinkedList;

public class Xml {
    public static final String encode="utf-8";



    protected XmlNode root;
    protected LinkedList<XmlNode> currentNodeStack;
    public Xml(XmlNode root){
        this.root=root;
        currentNodeStack=new LinkedList<>();
        if(root!=null)
            currentNodeStack.add(root);
    }

    public XmlNode getCurrentNode() {
        return currentNodeStack.getLast();
    }

    public void back(){
        if(currentNodeStack.size()>1){
            currentNodeStack.removeLast();
        }
    }

    public void into(int childrenIndex){
        XmlNode tmp=getCurrentNode().getChildAt(childrenIndex);
        if(tmp!=null){
            currentNodeStack.add(tmp);
        }
    }

    public int getCurrentStackSize(){
        return currentNodeStack.size();
    }

    public XmlNode getStackNodeAt(int index){
        return index<currentNodeStack.size()?currentNodeStack.get(index):null;
    }


    @Override
    public String toString(){
        return root==null?"":root.toString();
    }



    private static final int IN_NODE_NAME=1;//
    private static final int FIND_ATTR_K=2;//
    private static final int IN_ATTR_K=4;//
    private static final int FIND_ATTR_V=3;//
    private static final int IN_ATTR_V=5;//
    private static final int FIND_DATA=8;
    private static final int IN_DATA=9;
    private static final int TO_BE_END=10;
    private static final int IGNORED=11;
    public static XmlNode parseXml(String xml){
        int state = FIND_DATA;
        LinkedList<XmlNode> nodeStack=new LinkedList<>();
        XmlNode currentNode=null;
        StringBuilder nodeNameBuilder=new StringBuilder();
        StringBuilder attrKBuilder=new StringBuilder();
        StringBuilder attrVBuilder=new StringBuilder();
        StringBuilder dataBuilder=new StringBuilder();
        char preC='Y';
        for(int i=0;i<xml.length();++i){
            char c=xml.charAt(i);

            switch (state){
                case IN_NODE_NAME:
                    switch (c){
                        case '>':
                            state=FIND_DATA;
                            dataBuilder=new StringBuilder();
                            currentNode = XmlNode.EMPTY_NODE(nodeNameBuilder.toString());
                            if (!nodeStack.isEmpty()) {
                                nodeStack.getLast().addNode(currentNode);
                            }
                            nodeStack.add(currentNode);
                            break;
                        case ' ':
                        case '\r':
                        case '\n':
                            currentNode = XmlNode.EMPTY_NODE(nodeNameBuilder.toString());
                            if (!nodeStack.isEmpty()) {
                                nodeStack.getLast().addNode(currentNode);
                            }
                            nodeStack.add(currentNode);
                            state = FIND_ATTR_K;
                            break;
                        case '/':
                            if(preC!='<') {
                                currentNode = XmlNode.EMPTY_NODE(nodeNameBuilder.toString());
                                if (!nodeStack.isEmpty()) {
                                    nodeStack.getLast().addNode(currentNode);
                                }
                                nodeStack.add(currentNode);
                            }
                            state=TO_BE_END;
                            break;
                        case '?':
                            state=IGNORED;
                        default:
                            nodeNameBuilder.append(c);
                    }
                    break;
                case FIND_ATTR_K:
                    switch (c){
                        case '>':
                            state=FIND_DATA;
                            dataBuilder=new StringBuilder();
                        case ' ':
                        case '\r':
                        case '\n':
                            break;
                        case '/':state=TO_BE_END;break;
                        default:
                            attrKBuilder=new StringBuilder();
                            attrKBuilder.append(c);
                            state=IN_ATTR_K;
                    }
                    break;
                case IN_ATTR_K:
                    if(c != '='){
                        attrKBuilder.append(c);
                    }else {
                        state=FIND_ATTR_V;
                    }break;
                case FIND_ATTR_V:
                    if(c == '"'){
                        attrVBuilder=new StringBuilder();
                        state=IN_ATTR_V;
                    }break;
                case IN_ATTR_V:
                    if(c != '"'){
                        attrVBuilder.append(c);
                    }else {
                        currentNode.setAttr(attrKBuilder.toString(),attrVBuilder.toString());
                        state=FIND_ATTR_K;
                    }break;
                case FIND_DATA:
                    switch (c){
                        case ' ':
                        case '\r':
                        case '\n':break;
                        case '<':
                            nodeNameBuilder = new StringBuilder();
                            state=IN_NODE_NAME;
                            break;
                        default:
                            dataBuilder=new StringBuilder();
                            dataBuilder.append(c);
                            state=IN_DATA;
                    }break;
                case IN_DATA:
                    switch (c){
                        case '<':
                            state=TO_BE_END;
                            currentNode.setData(dataBuilder.toString().trim());
                            break;
                        default:
                            dataBuilder.append(c);
                    }
                case TO_BE_END:
                    switch (c){
                        case '>':
                            nodeStack.remove(currentNode);
                            if(nodeStack.isEmpty())
                                return currentNode;
                            else
                                currentNode=nodeStack.getLast();
                            state=FIND_DATA;
                    }
                case IGNORED:
                    if(c=='>')
                        state=FIND_DATA;
                    break;
            }
            preC=c;

        }
        return currentNode;
    }
}
